Skip to content

fix: Strip undefined values in ParseServerRESTController to match HTTP mode#10269

Open
yog27ray wants to merge 10 commits intoparse-community:alphafrom
yog27ray:fix/directaccess-undefined-to-null
Open

fix: Strip undefined values in ParseServerRESTController to match HTTP mode#10269
yog27ray wants to merge 10 commits intoparse-community:alphafrom
yog27ray:fix/directaccess-undefined-to-null

Conversation

@yog27ray
Copy link
Contributor

@yog27ray yog27ray commented Mar 21, 2026

Issue

When directAccess: true is enabled, ParseServerRESTController bypasses the HTTP layer and routes requests internally. This causes two categories of behavior divergence from the HTTP code path:

1. Undefined values in request body stored as null

undefined values in update (PUT) payloads are converted to null and stored in the database. This breaks doesNotExist / exists queries and changes data semantics.

Root cause: ParseServerRESTController passes request data directly to Parse Server internals without JSON serialization. In HTTP mode, JSON.stringify() naturally strips undefined values from payloads. With directAccess, undefined values are preserved all the way to transformUpdate() in MongoTransform.js, which passes them into MongoDB's $set operator. The MongoDB BSON driver then converts undefined to null.

2. Undefined values in response leak to the client

Cloud function responses containing undefined values (e.g., { questionId: undefined }) are passed directly in-memory to the caller without JSON serialization. In HTTP mode, JSON.stringify naturally strips these undefined properties. With directAccess, the caller receives the raw response object with undefined properties present, causing deep.equal assertions and object key checks to behave differently.

Reproduction (response issue):

// Cloud function that returns undefined values
Parse.Cloud.define('myFunction', () => {
  return {
    definedKey: 'value',
    undefinedKey: undefined,          // should be stripped
    nested: { a: 1, b: undefined },  // b should be stripped
    arrayWithUndefined: [1, undefined, 3],  // undefined → null
  };
});

// With HTTP: { definedKey: 'value', nested: { a: 1 }, arrayWithUndefined: [1, null, 3] }
// With directAccess (before fix): { definedKey: 'value', undefinedKey: undefined, nested: { a: 1, b: undefined }, arrayWithUndefined: [1, undefined, 3] }

Approach

The stripUndefined() helper in ParseServerRESTController.js is now applied to both the request body AND the response:

  1. Request body : body: stripUndefined(data) — strips undefined values before passing to the router, matching HTTP's JSON.stringify behavior on the request path.

  2. Response : stripUndefined(response) — strips undefined values from the response before resolving to the caller, matching HTTP's JSON serialization behavior on the response path.

This ensures directAccess behaves identically to HTTP mode in both directions. The fix is applied at the ParseServerRESTController level — the component added when directAccess is enabled — addressing the root cause at the right layer.

Tasks

  • Add tests
  • Add changes to documentation (guides, repository pages, code comments)
  • Add security check

…TTP mode behavior

When `directAccess: true` is enabled, `ParseServerRESTController` passes
request data directly to Parse Server internals without JSON serialization.
In HTTP mode, `JSON.stringify()` naturally strips `undefined` values from
payloads. With directAccess, `undefined` values are preserved, passed to
MongoDB's `$set` operator, and converted to `null` by the BSON driver.

This causes fields that should be absent to be stored as `null`, breaking
`doesNotExist` queries and changing data semantics.

The fix adds a `stripUndefined()` helper that removes keys with `undefined`
values from the request body, making directAccess behave identically to
HTTP mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@parse-github-assistant
Copy link

I will reformat the title to use the proper commit message syntax.

@parse-github-assistant parse-github-assistant bot changed the title fix: strip undefined values in ParseServerRESTController to match HTTP mode fix: Strip undefined values in ParseServerRESTController to match HTTP mode Mar 21, 2026
@parse-github-assistant
Copy link

parse-github-assistant bot commented Mar 21, 2026

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

Tip

  • Keep pull requests small. Large PRs will be rejected. Break complex features into smaller, incremental PRs.
  • Use Test Driven Development. Write failing tests before implementing functionality. Ensure tests pass.
  • Group code into logical blocks. Add a short comment before each block to explain its purpose.
  • We offer conceptual guidance. Coding is up to you. PRs must be merge-ready for human review.
  • Our review focuses on concept, not quality. PRs with code issues will be rejected. Use an AI agent.
  • Human review time is precious. Avoid review ping-pong. Inspect and test your AI-generated code.

Note

Please respond to review comments from AI agents just like you would to comments from a human reviewer. Let the reviewer resolve their own comments, unless they have reviewed and accepted your commit, or agreed with your explanation for why the feedback was incorrect.

Caution

Pull requests must be written using an AI agent with human supervision. Pull requests written entirely by a human will likely be rejected, because of lower code quality, higher review effort and the higher risk of introducing bugs. Please note that AI review comments on this pull request alone do not satisfy this requirement.

@coderabbitai
Copy link

coderabbitai bot commented Mar 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a recursive stripUndefined helper that omits object properties with undefined and converts undefined in arrays to null; applies it to outbound request bodies. Adds two tests verifying PUT with absentField: undefined preserves omission on subsequent GET.

Changes

Cohort / File(s) Summary
Test Coverage
spec/ParseServerRESTController.spec.js
Added two integration tests: create MyObject, perform PUT update including absentField: undefined (once via RESTController.request, once via HTTP request helper), then GET to assert absentField is absent from responses.
REST Request Processing
src/ParseServerRESTController.js
Introduced stripUndefined(obj) that recursively omits object properties with undefined and converts undefined in arrays to null. Updated handleRequest to pass stripUndefined(data) into outbound request.body.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding undefined value stripping to ParseServerRESTController to align directAccess behavior with HTTP mode.
Description check ✅ Passed The PR description is comprehensive, addressing the issue, root cause, approach, and completed tasks with clear examples and test cases.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Mar 21, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
spec/ParseServerRESTController.spec.js (1)

679-695: Add one nested undefined regression case to close the parity gap.

This test is good for top-level fields. Please add a case like { nested: { absentField: undefined } } to ensure nested updates also match HTTP behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/ParseServerRESTController.spec.js` around lines 679 - 695, Add a
nested-undefined regression case to the existing test "should not convert
undefined values to null on update with directAccess" in
ParseServerRESTController.spec.js: after the current PUT that includes
absentField: undefined, also perform a PUT (or include in the same update) with
a nested object like { nested: { absentField: undefined } } and then GET the
object and assert that getRes.nested is defined, getRes.nested.absentField is
undefined, and that 'absentField' is not an own property of getRes.nested;
update expectations in the test to cover both top-level and nested undefined
behavior so nested updates mirror HTTP behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ParseServerRESTController.js`:
- Around line 32-43: The stripUndefined function currently only removes
undefined at the top level; update stripUndefined to recursively traverse
objects and arrays so its behavior matches JSON.stringify: for objects (function
stripUndefined) iterate keys and if a value is undefined omit the key, otherwise
set key to stripUndefined(value); for arrays return a new array mapping each
element to stripUndefined(element) but convert undefined elements to null (i.e.,
if element === undefined return null); preserve primitives and null as-is and
avoid mutating the original input. Ensure the function handles nested
objects/arrays and returns a fresh structure.

---

Nitpick comments:
In `@spec/ParseServerRESTController.spec.js`:
- Around line 679-695: Add a nested-undefined regression case to the existing
test "should not convert undefined values to null on update with directAccess"
in ParseServerRESTController.spec.js: after the current PUT that includes
absentField: undefined, also perform a PUT (or include in the same update) with
a nested object like { nested: { absentField: undefined } } and then GET the
object and assert that getRes.nested is defined, getRes.nested.absentField is
undefined, and that 'absentField' is not an own property of getRes.nested;
update expectations in the test to cover both top-level and nested undefined
behavior so nested updates mirror HTTP behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 40b804d3-ed21-4a29-b323-4dff6e0f2cc2

📥 Commits

Reviewing files that changed from the base of the PR and between e8cc676 and a423f0c.

📒 Files selected for processing (2)
  • spec/ParseServerRESTController.spec.js
  • src/ParseServerRESTController.js

@codecov
Copy link

codecov bot commented Mar 21, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.53%. Comparing base (92c14ba) to head (d26fe93).

Additional details and impacted files
@@            Coverage Diff             @@
##            alpha   #10269      +/-   ##
==========================================
- Coverage   92.53%   92.53%   -0.01%     
==========================================
  Files         192      192              
  Lines       16500    16510      +10     
  Branches      227      227              
==========================================
+ Hits        15268    15277       +9     
- Misses       1212     1213       +1     
  Partials       20       20              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

JSON.parse(JSON.stringify(data)) perfectly replicates HTTP mode's
serialization roundtrip — stripping undefined at all nesting levels,
not just top-level keys.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 21, 2026
…ingify())

JSON.parse(JSON.stringify()) is expensive. Replace with a recursive
stripUndefined that walks the object tree efficiently — strips undefined
keys from objects at all nesting levels and converts undefined array
elements to null, matching JSON.stringify behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 21, 2026
Copy link
Member

@mtrezza mtrezza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test to compare that with behavior when directAccess is false? It should yield the same result.

Adds a companion test that verifies the same behavior without directAccess
(HTTP mode) to confirm both paths yield identical results, as requested
in review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@yog27ray
Copy link
Contributor Author

yog27ray commented Mar 23, 2026

@mtrezza Added a companion test that performs the same create/update/fetch via HTTP (non-directAccess) to confirm both paths yield identical results. The test lets the HTTP layer handle JSON serialization naturally (via encodeBody), so undefined values are stripped by the inherent JSON.stringify in HTTP mode — matching the stripUndefined behavior in directAccess mode.

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 23, 2026
Remove explicit JSON.stringify/JSON.parse calls from the HTTP mode test,
letting the request helper's encodeBody handle serialization naturally.
This better demonstrates that HTTP mode inherently strips undefined values.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 23, 2026
@mtrezza
Copy link
Member

mtrezza commented Mar 23, 2026

@coderabbitai Always review the PR title as well. If the commit prefix is fix:, like in this case, then the PR title must describe the bug (broken behavior), not the fix (expected behavior).

@coderabbitai
Copy link

coderabbitai bot commented Mar 23, 2026

Tip

For best results, initiate chat on the files or code changes.

@mtrezza, understood — thank you for the clarification!

The current title "fix: Strip undefined values in ParseServerRESTController to match HTTP mode" describes the fix. Here's a suggested revision that describes the broken behavior instead:

fix: directAccess update with undefined field values persists them as null in MongoDB

Or alternatively:

fix: Undefined values in directAccess PUT requests stored as null, breaking exists/doesNotExist queries

I'll record this convention for future reviews.


✏️ Learnings added
Learnt from: mtrezza
URL: https://github.com/parse-community/parse-server/pull/10269

Timestamp: 2026-03-23T22:11:01.891Z
Learning: In Parse Server PRs (parse-community/parse-server), when the commit/PR type prefix is `fix:`, the PR title must describe the bug (the broken behavior), not the fix (the expected or corrected behavior). For example, instead of "fix: Strip undefined values in ParseServerRESTController to match HTTP mode" (describes the fix), prefer "fix: directAccess update with undefined field values persists them as null in MongoDB" (describes the broken behavior).

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: mtrezza
Repo: parse-community/parse-server PR: 10237
File: src/Routers/PagesRouter.js:552-573
Timestamp: 2026-03-18T17:53:35.669Z
Learning: In parse-community/parse-server, `src/Routers/PagesRouter.js`: the `encodeURIComponent` call in `composePageParamHeaders` is pre-existing behavior extracted from inline code in `pageResponse` and `redirectResponse`. The potential URIError on malformed Unicode (lone surrogates) when `encodePageParamHeaders` is enabled is a known pre-existing issue, not introduced by the CRLF sanitization refactor. Do not flag it as a new issue in PRs that only refactor or consolidate this call.

Learnt from: mtrezza
Repo: parse-community/parse-server PR: 0
File: :0-0
Timestamp: 2025-11-17T15:02:48.786Z
Learning: For Parse Server PRs, always suggest an Angular commit convention PR title that would make a meaningful changelog entry for developers. Update the PR title suggestion on every commit. The format should be: type(scope): description. Common types include feat, fix, perf, refactor, docs, test, chore. The scope should identify the subsystem (e.g., graphql, rest, push, security). The description should be action-oriented and clearly convey the change's impact to developers.

Learnt from: mtrezza
Repo: parse-community/parse-server PR: 0
File: :0-0
Timestamp: 2025-11-17T15:02:24.824Z
Learning: For Parse Server PRs, always suggest an Angular-style PR title that would make a meaningful changelog entry for developers. Update the PR title suggestion with every new commit to the PR.

Learnt from: mtrezza
Repo: parse-community/parse-server PR: 10161
File: src/Adapters/Storage/Postgres/PostgresStorageAdapter.js:1743-1750
Timestamp: 2026-03-09T21:11:01.966Z
Learning: In `src/Adapters/Storage/Postgres/PostgresStorageAdapter.js` (`updateObjectsByQuery`), the nested sub-key `c` (derived from dot-notation originalUpdate keys) is directly interpolated into SQL string literals in the Object Increment path (`->>'${c}'` and `CONCAT('{"${c}":', ...)`). This is a confirmed SQL injection vulnerability tracked as GHSA-gqpp-xgvh-9h7h and will be fixed in a separate dedicated PR. The maintainer (mtrezza) prefers a simpler fix — either single-quote escaping (`c.replace(/'/g, "''")`) or strict key name validation (e.g., `/^[A-Za-z0-9_]+$/`) — over restructuring with `jsonb_build_object`, to minimize SQL structure changes. Do not suggest `jsonb_build_object` for this fix.

Learnt from: mtrezza
Repo: parse-community/parse-server PR: 0
File: :0-0
Timestamp: 2025-11-08T13:46:04.940Z
Learning: When reviewing Parse Server PRs that add new features, always check whether the feature is documented in the README.md file, though for new Parse Server options this is optional rather than required.

Learnt from: mtrezza
Repo: parse-community/parse-server PR: 10128
File: src/Routers/PagesRouter.js:111-112
Timestamp: 2026-03-07T19:09:01.305Z
Learning: In Parse Server (parse-community/parse-server), the PagesRouter (src/Routers/PagesRouter.js) is mounted behind `express.urlencoded({ extended: false })` (src/ParseServer.ts ~line 333). This means request body values are always flat strings — nested objects in req.body are not possible for this router. The `rawToken && typeof rawToken !== 'string' ? rawToken.toString() : rawToken` guard pattern used consistently across verifyEmail (line ~87), resendVerificationEmail (line ~112), requestResetPassword (line ~154), and resetPassword (line ~183) is therefore safe; the malicious-object scenario (e.g., `{ toString: 'x' }`) is unreachable.

Learnt from: mtrezza
Repo: parse-community/parse-server PR: 10149
File: src/Controllers/DatabaseController.js:1865-1873
Timestamp: 2026-03-08T21:51:35.204Z
Learning: In `src/Controllers/DatabaseController.js` (`performInitialization`), the `ensureAuthDataUniqueness` index creation is intentionally non-fatal (warn-only, no rethrow). Unlike username/email uniqueness which has been enforced since Parse Server's inception, authData uniqueness is new. Existing production databases may have duplicate authData entries caused by the race condition this PR fixes, so crashing on startup would be a breaking change. The application-level `ensureUniqueAuthDataId` check in `RestWrite.js` still runs as a fallback, and the warning log gives operators visibility to resolve duplicates manually before the index can be created successfully.

Learnt from: Moumouls
Repo: parse-community/parse-server PR: 10204
File: src/Security/CheckGroups/CheckGroupServerConfig.js:145-147
Timestamp: 2026-03-14T21:39:34.558Z
Learning: In Parse Server's `src/Security/CheckGroups/CheckGroupServerConfig.js`, the "Request complexity limits enabled" security check intentionally uses `return` (not `throw 1`) when `requestComplexity` is `undefined` in PS9. This is by design: `requestComplexity` is opt-in in the current major version and will only become a hard-failing security check (throw 1 when undefined) in PS10. The deprecation DEPPS18 in `src/Deprecator/Deprecations.js` communicates this upcoming default change to operators.

Learnt from: mtrezza
Repo: parse-community/parse-server PR: 0
File: :0-0
Timestamp: 2025-12-02T06:55:53.808Z
Learning: When reviewing Parse Server PRs that add or modify Parse Server options, always verify that changes are properly reflected in three files: src/Options/index.js (where changes originate), src/Options/Definitions.js, and src/Options/docs.js. The correct workflow is: make changes in index.js first, then run `npm run definitions` to automatically replicate the changes to Definitions.js and docs.js.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants